﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace Referee
{
    class Reader
    {
        string connstr;
        int heartbeat;
        int rid;
        int? eid;
        int? cid;

        public Reader(string connstr, int heartbeat)
        {
            this.connstr = connstr;
            this.heartbeat = heartbeat;
        }

        public JudgeKernel.Code WaitCode()
        {
            GC.Collect();
            var sql = new Sql.DatabaseDataContext(connstr);
            while (true)
            {
                // Contest
                var code = (from s in sql.ContestStatus
                            from p in sql.ContestProblem
                            where (s.status == 0 || (s.status == 1 || s.status == 2) && (DateTime.Now - s.submitTime).TotalMinutes > 5) && p.cid == s.cid && p.ps == s.ps
                            orderby s.status, s.rid
                            select new JudgeKernel.Code
                            {
                                cid = s.cid,
                                eid = null,
                                rid = s.rid,
                                username = s.username,
                                code = s.code,
                                lang = s.lang,
                                timeLimit = p.timeLimit,
                                memoryLimit = p.memoryLimit,
                                inputData = p.inputData,
                                outputaData = p.outputData,
                                specialJudge = p.specialJudge,
                                specialLang = p.specialLang,
                                specialCode = p.specialCode
                            }).FirstOrDefault();
                if (code != null)
                {
                    rid = code.rid;
                    cid = code.cid;
                    return code;
                }
                // Normal
                code = (from s in sql.Status
                            from p in sql.Problem
                            where (s.status == 0 || (s.status == 1 || s.status == 2) && (DateTime.Now - s.submitTime).TotalMinutes > 5) && p.pid == s.pid
                            orderby s.status, s.rid
                            select new JudgeKernel.Code
                            {
                                cid = null,
                                eid = null,
                                rid = s.rid,
                                username = s.username,
                                code = s.code,
                                lang = s.lang,
                                timeLimit = p.timeLimit,
                                memoryLimit = p.memoryLimit,
                                inputData = p.inputData,
                                outputaData = p.outputData,
                                specialJudge = p.specialJudge,
                                specialLang = p.specialLang,
                                specialCode = p.specialCode
                            }).FirstOrDefault();
                if (code != null)
                {
                    rid = code.rid;
                    eid = null;
                    return code;
                }

                // Experiment
                code = (from s in sql.DSStatus
                        from p in sql.DSProblem
                        where (s.status == 0 || (s.status == 1 || s.status == 2) && (DateTime.Now - s.submitTime).TotalMinutes > 5) && p.eid == s.eid && p.pid == s.pid
                        orderby s.status, s.rid
                        select new JudgeKernel.Code
                        {
                            cid = null,
                            eid = s.eid,
                            rid = s.rid,
                            username = s.username,
                            code = s.code,
                            lang = s.lang,
                            timeLimit = p.timeLimit,
                            memoryLimit = p.memoryLimit,
                            inputData = p.inputData,
                            outputaData = p.outputData,
                            specialJudge = p.specialJudge,
                            specialLang = p.specialLang,
                            specialCode = p.specialCode
                        }).FirstOrDefault();
                if (code != null)
                {
                    rid = code.rid;
                    eid = code.eid;
                    return code;
                }

                Thread.Sleep(heartbeat);
            }
        }

        public bool SetStatus(int status)
        {
            var sql = new Sql.DatabaseDataContext(connstr);
            if (cid != null) // Contest
            {
                var s = sql.ContestStatus.FirstOrDefault(row => row.rid == rid);
                if (s == null) return false;
                s.status = status;
            }
            else if (eid == null) // Normal
            {
                var s = sql.Status.FirstOrDefault(row => row.rid == rid);
                if (s == null) return false;
                s.status = status;
            }
            else // Experiment
            {
                var s = sql.DSStatus.FirstOrDefault(row => row.rid == rid);
                if (s == null) return false;
                s.status = status;
            }
            sql.SubmitChanges();
            return true;
        }

        public bool SaveResult(JudgeKernel.Result result)
        {
            var sql = new Sql.DatabaseDataContext(connstr);
            if (cid != null) // Contest
            {
                var s = sql.ContestStatus.FirstOrDefault(row => row.rid == rid);
                if (s == null) return false;
                s.status = result.status;
                s.exeTime = result.exeTime;
                s.exeMem = result.exeMem;
                s.ceInfo = result.ceInfo;
                s.exeOutput = result.exeOutput;
                s.specialOutput = result.specialOutput;
                if (!s.god)
                {
                    var cps = sql.ContestProblemStatistics.FirstOrDefault(row => row.ps == s.ps && row.cid == cid);
                    if (cps != null)
                        updateContestProblemStatistics(cps, result.status);

                    var so = sql.ContestSolve.FirstOrDefault(row => row.username == s.username && row.ps == s.ps && row.cid == cid);
                    
                    if (so == null)
                    {
                        so = new Sql.ContestSolve
                        {
                            cid = (int)cid,
                            username = s.username,
                            ps = s.ps,
                            submit = 0,
                            ac = 0,
                            bestRid = result.status == 3 ? (int?)s.rid : null
                        };
                        sql.ContestSolve.InsertOnSubmit(so);
                    }
                    ++so.submit;
                    if (result.status == 3)
                    {
                        ++so.ac;
                        if (so.bestRid == null) so.bestRid = s.rid;
                        else
                        {
                            var s2 = sql.Status.FirstOrDefault(row => row.rid == so.bestRid);
                            if (s2 == null) so.bestRid = s.rid;
                            else if (s.exeTime < s2.exeTime || s.exeTime == s2.exeTime &&
                                (s.exeMem < s2.exeMem || s.exeMem == s2.exeMem &&
                                (s.code.Length < s2.code.Length)))
                                so.bestRid = s.rid;
                        }
                    }
                    sql.SubmitChanges();
                    var sts = sql.ContestStatus.FirstOrDefault(row => row.rid == rid);
                    if (sts == null) return false;
                    bool tmp = contest.updataRanklist(sts, connstr);
                    if (tmp == false) return false;
                }
                
            }
            else if (eid == null) // Normal
            {
                var s = sql.Status.FirstOrDefault(row => row.rid == rid);
                if (s == null) return false;
                s.status = result.status;
                s.exeTime = result.exeTime;
                s.exeMem = result.exeMem;
                s.ceInfo = result.ceInfo;
                s.reCode = result.reCode;
                s.exeOutput = result.exeOutput;
                s.specialOutput = result.specialOutput;

                
                if (!s.god)
                {
                    var us = sql.UserStatistics.FirstOrDefault(row => row.username == s.username);
                    if (us != null)
                        updateUserStatistics(us, result.status);
                    var ps = sql.ProblemStatistics.FirstOrDefault(row => row.pid == s.pid);
                    if (ps != null)
                        updateProblemStatistics(ps, result.status);

                    var so = sql.Solve.FirstOrDefault(row => row.username == s.username && row.pid == s.pid);
                    if (so == null)
                    {
                        so = new Sql.Solve
                        {
                            username = s.username,
                            pid = s.pid,
                            submit = 1,
                            accept = result.status == 3 ? 1 : 0,
                            bestRid = result.status == 3 ? (int?)s.rid : null
                        };
                        sql.Solve.InsertOnSubmit(so);
                    }
                    else
                    {
                        ++so.submit;
                        if (result.status == 3)
                        {
                            ++so.accept;
                            if (so.bestRid == null) so.bestRid = s.rid;
                            else
                            {
                                var s2 = sql.Status.FirstOrDefault(row => row.rid == so.bestRid);
                                if (s2 == null) so.bestRid = s.rid;
                                else if (s.exeTime < s2.exeTime || s.exeTime == s2.exeTime &&
                                    (s.exeMem < s2.exeMem || s.exeMem == s2.exeMem &&
                                    (s.code.Length < s2.code.Length)))
                                    so.bestRid = s.rid;
                            }
                        }
                    }
                }
            }
            else // Experiment
            {
                var s = sql.DSStatus.FirstOrDefault(row => row.rid == rid);
                if (s == null) return false;
                s.status = result.status;
                s.exeTime = result.exeTime;
                s.exeMem = result.exeMem;
                s.ceInfo = result.ceInfo;
                s.reCode = result.reCode;
                s.exeOutput = result.exeOutput;
                s.specialOutput = result.specialOutput;

                if (!s.god)
                {
                    var ps = sql.DSProblemStatistics.FirstOrDefault(row => row.eid == s.eid && row.pid == s.pid);
                    if (ps != null)
                        updateDSProblemStatistics(ps, result.status);

                    var so = sql.DSSolve.FirstOrDefault(row => row.username == s.username && row.eid == s.eid && row.pid == s.pid);

                    double sim = 0;
                    int? simrid = null;
                    if (result.status == 3 && s.sid != null) // 相似度
                    {
                        var hiss = sql.DSStatus.Where(row => row.eid == s.eid && row.pid == s.pid && row.sid != null && row.status == 3 && row.username != s.username && !row.god);
                        foreach (var his in hiss)
                        {
                            var hiso = sql.DSSolve.FirstOrDefault(row => row.eid == s.eid && row.pid == s.pid && row.username == his.username);
                            double hisim;
                            var mysim = Similarity.getSimilarity(s.code, his.code, out hisim);
                            if (mysim > sim)
                            {
                                sim = mysim;
                                simrid = his.rid;
                            }
                            if (hisim > hiso.similarityDegree)
                            {
                                hiso.similarityDegree = (float)hisim;
                                hiso.similarityRid = s.rid;
                            }
                        }
                    }

                    if (so == null)
                    {
                        so = new Sql.DSSolve
                        {
                            username = s.username,
                            sid = s.sid,
                            eid = s.eid,
                            pid = s.pid,
                            submit = 1,
                            accept = result.status == 3 ? 1 : 0,
                            bestRid = result.status == 3 ? (int?)s.rid : null,
                            similarityDegree = (float)sim,
                            similarityRid = simrid
                        };
                        sql.DSSolve.InsertOnSubmit(so);
                    }
                    else
                    {
                        ++so.submit;
                        if (result.status == 3)
                        {
                            ++so.accept;
                            if (so.bestRid == null) so.bestRid = s.rid;
                            else
                            {
                                var s2 = sql.DSStatus.FirstOrDefault(row => row.rid == so.bestRid);
                                if (s2 == null) so.bestRid = s.rid;
                                else if (s.exeTime < s2.exeTime || s.exeTime == s2.exeTime &&
                                    (s.exeMem < s2.exeMem || s.exeMem == s2.exeMem &&
                                    (s.code.Length < s2.code.Length)))
                                    so.bestRid = s.rid;
                            }
                            if (so.sid != null && so.similarityDegree < sim)
                            {
                                so.similarityDegree = (float)sim;
                                so.similarityRid = simrid;
                            }
                        }
                    }
                }
            }

            sql.SubmitChanges();
            return true;
        }

        internal static void updateUserStatistics(Sql.UserStatistics us, int status)
        {
            if (status >= 3) ++us.submit;
            switch (status)
            {
                case 3:
                    ++us.accept;
                    break;
                case 4:
                    ++us.pe;
                    break;
                case 5:
                    ++us.tle;
                    break;
                case 6:
                    ++us.mle;
                    break;
                case 7:
                    ++us.ole;
                    break;
                case 8:
                    ++us.wa;
                    break;
                case 9:
                    ++us.re;
                    break;
                case 10:
                    ++us.ce;
                    break;
                case 11:
                    ++us.se;
                    break;
            }
        }

        internal static void updateProblemStatistics(Sql.ProblemStatistics ps, int status)
        {
            if (status >= 3) ++ps.submit;
            switch (status)
            {
                case 3:
                    ++ps.accept;
                    break;
                case 4:
                    ++ps.pe;
                    break;
                case 5:
                    ++ps.tle;
                    break;
                case 6:
                    ++ps.mle;
                    break;
                case 7:
                    ++ps.ole;
                    break;
                case 8:
                    ++ps.wa;
                    break;
                case 9:
                    ++ps.re;
                    break;
                case 10:
                    ++ps.ce;
                    break;
                case 11:
                    ++ps.se;
                    break;
            }
        }
        internal static void updateContestProblemStatistics(Sql.ContestProblemStatistics cps, int status)
        {
            if (status >= 3) ++cps.submit;
            switch (status)
            {
                case 3:
                    ++cps.accept;
                    break;
            }
        }
        internal static void updateDSProblemStatistics(Sql.DSProblemStatistics ps, int status)
        {
            if (status >= 3) ++ps.submit;
            switch (status)
            {
                case 3:
                    ++ps.accept;
                    break;
            }
        }
    }
}
